package at.horario.service;

//~--- non-JDK imports --------------------------------------------------------

import at.horario.model.algo.*;
import static at.horario.model.algo.Quadro.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Random;

/**
 * Class description
 *
 * @author AT�
 * @version 1.0, 2007.05.12 at 10:34:08 CAT
 */
public class AlgoritmosManager2 {
    public static final String[] NOMES_DIAS_SEMANA = {"Segunda Feira", "Terca Feira", "Quarta Feira", "Quinta Feira", "Sexta Feira"};
    private HorarioAlgo horarioAlgo;
    private int tamanho_LRC;
    private int buscaTabuMax;
    private int tamanhoListaTabu;
    private final static int DIAS_SEMANA = 5;
    public final static int TEMPOS_DIA = 6;

    public final static int HORARIOS_SEMANA = TEMPOS_DIA * DIAS_SEMANA;
    private int pesoGap;
    private int pesoExcessoAula;
    private int pesoSobreposicao;
    private int graspMAX;


    public void setGraspMAX(int graspMAX) {
        this.graspMAX = graspMAX;
    }

    public AlgoritmosManager2() {
    }


    public int getPesoGap() {
        return pesoGap;
    }

    public void setPesoGap(int pesoGap) {
        this.pesoGap = pesoGap;
    }

    public int getPesoExcessoAula() {
        return pesoExcessoAula;
    }

    public void setPesoExcessoAula(int pesoExcessoAula) {
        this.pesoExcessoAula = pesoExcessoAula;
    }

    public int getPesoSobreposicao() {
        return pesoSobreposicao;
    }

    public void setPesoSobreposicao(int pesoSobreposicao) {
        this.pesoSobreposicao = pesoSobreposicao;
    }

    public void setTamanhoListaTabu(int tamanhoListaTabu) {
        this.tamanhoListaTabu = tamanhoListaTabu;
    }

    public void setTamanho_LRC(int tamanho_LRC) {
        this.tamanho_LRC = tamanho_LRC;
    }


    public void setBuscaTabuMax(int buscaTabuMax) {
        this.buscaTabuMax = buscaTabuMax;
    }

    /**
     * Method description
     *
     * @param horarioSettings
     */

    /**
     * Method description
     *
     * @param horarioAlgo
     */
    public void setHorario(HorarioAlgo horarioAlgo) {
        this.horarioAlgo = horarioAlgo;
    }

    /**
     * Method description
     *
     * @return
     */
    public Quadro construaSolucaoInicial() {
        Quadro s = new Quadro(horarioAlgo.getNumProfessores(), HORARIOS_SEMANA, TEMPOS_DIA);
        int[] horarioCriticidades = new int[HORARIOS_SEMANA];    // criticidade dos horarios

        // em principio todos professores est�o disponiveis por isso , o num�ro de professores indisponiveis � igual a 0
        int[] ordenaHC = new int[HORARIOS_SEMANA];    // ordena horarios segundo a criticidade, ordem decrescente

        for (int i = 0; i < ordenaHC.length; i++) {
            ordenaHC[i] = i;    // ordem normal dado que os horarios tem a mesma criticidade
        }

        ProfCH[] aulas = new ProfCH[horarioAlgo.getNumProfessores()];
        int numAulas = horarioAlgo.getNumProfessores();
        int total = 0;

        for (int i = 0; i < horarioAlgo.getNumProfessores(); i++) {
            ProfessorAlgo pa = horarioAlgo.getProfessores()[i];
            ProfCH profCH = new ProfCH();


            for (int j = 0; j < pa.getIndisponibilidades().length; j++) {
                int h = pa.getIndisponibilidades()[j];
                s.getMatriz()[pa.getNum() - 1][h] = -20;
            }


            profCH.setCargaHoraria(pa.getCh());
            profCH.setProfessor(pa.getNum());
            profCH.setTurmas(new ArrayList<TurmaCH>());

            int chProfessor = 0;

            for (int j = 0; j < pa.getTurmas().length; j++) {
                int turma = pa.getTurmas()[j];
                int ch = pa.getCargaHorariaTurma(j);

                profCH.getTurmas().add(new TurmaCH(turma, ch));
                chProfessor += ch;
            }
            profCH.setCargaHoraria(chProfessor);

            aulas[i] = profCH;
            total += profCH.getCargaHoraria();
        }

        Arrays.sort(aulas);    // ordenar os Professores segundo a carga hor�ria

        Random oRandom = new Random();

        while (total-- > 0) {
            int virtualEscolhida = oRandom.nextInt((tamanho_LRC <= numAulas)
                    ? tamanho_LRC
                    : numAulas);                            // tamanho dinamico depende do tamanho da lista
            int escolhida = 0;
            for (int j = 0; j <= virtualEscolhida; escolhida++) {
                if (!aulas[escolhida].getTurmas().isEmpty()) {
                    j++;
                }
            }
            escolhida--;
            ProfCH profCH = aulas[escolhida];
            int professor = profCH.getProfessor() - 1;
            //System.out.println(profCH.getTurmas().size());
            int innerEscolhida = oRandom.nextInt(profCH.getTurmas().size());
            TurmaCH turmaCH = profCH.getTurmas().get(innerEscolhida);

            profCH.addCargaHoraria();
            turmaCH.subCargaHoraria();

            if (turmaCH.getCargaHoraria() == 0) {               // se j� tiver alocado todas as numeroTurmas
                profCH.getTurmas().remove(innerEscolhida);    // vamos remover
                if (profCH.getTurmas().isEmpty()) {
                    //aulas[escolhida] = null;
                    numAulas--;
                }
            }
            ordernarAulas(aulas, professor);

            // agora vamos tentar encaixar em um dos hor�rios
            int t = 3;                                              /*
                                                                               *  o valor de t representa o tipo de aloca��o
                                                                               * 3 se a aula � alocada sem inviabilidade
                                                                               * 2 se a aula � alocada com inviabilidade do tipo 2
                                                                               * 1 se a aula � alocada com inviabilidade do tipo 1
                                                                               */
            boolean alocada = false;

            while (!alocada) {
                for (int i = 0; (i < ordenaHC.length) && !alocada; i++) {    // do horario + critico ao menos critico
                    int h = ordenaHC[i];                                      // hor�rio par alocar a turma

                    if (s.getMatriz()[professor][h] == 0) {//se estiver disponivel
                        s.getMatriz()[professor][h] = turmaCH.getTurma();

                        switch (t) {
                            case 3:// nao adminitimos nenhum restri��o
                                int sobreposicao = calculaSobreposicao(s, professor, h);

                                if (sobreposicao > 0) {
                                    break;
                                }

                                int excessoAulas = calculaExcessoAulas(s, professor, h);

                                if (excessoAulas > 0) {
                                    break;
                                }

                                alocada = true;

                                break;

                            case 2:                                              // admitimos so excesso de aulas
                                sobreposicao = calculaSobreposicao(s, professor, h);

                                if (sobreposicao > 0) {
                                    break;
                                }

                                alocada = true;
                            default:                                             // admitimos a sobreposicao e o excesso de aulas
                                alocada = true;
                        }// switch

                        if (!alocada) {
                            s.getMatriz()[professor][h] = 0;
                        } else {
                            horarioCriticidades[h]++;//hor�rio aumentou
                            ordernarHC(ordenaHC, horarioCriticidades, h);

                        }
                    }
                }// for
                t--;
            } // while
        }

        return s;
    }

    public void avaliar(Quadro s) {

        /*
         * Criar um array um para guardar as colunas e ordena-las
         */
        int[] colunas = new int[s.matriz.length];

        s.setSobreposicao(0);
        s.setExcessoaulas(0);
        s.setGaps(0);
        s.setValor(0);

        /*
         * Sobreposicao
         */
        for (int h = 0; h < s.matriz[0].length; h++) {    // percorrer os horarios
            s.sobreposicao += calculaSobreposicao(s, h);
        }

        /*
         * Excesso dia,
         */
        for (int i = 0; i < s.matriz.length; i++) {    // percoerr as linhas=professores
            calculaGapsAndExcessoaulas(s, i);

            // if (tempGaps > 0) System.out.println("Gap:"+i);
            // if (tempExcessoaulas > 0) System.out.println("TempExcessoaulas:"+i);
            s.gaps += s.tempGaps;
            s.excessoaulas += s.tempExcessoaulas;
        }

        s.valor = PESO_GAP * s.getGaps() + PESO_EXCESSO_AULA * s.getExcessoaulas() + PESO_SOBREPOSICAO * s.getSobreposicao();
    }


    private void ordernarHC(int[] ordenaHC, int[] horarioCriticidades, int h) {
        for (int i = h; i > 0; i--) {
            if (horarioCriticidades[i] > horarioCriticidades[i - 1]) {
                int temp = ordenaHC[i];
                ordenaHC[i] = ordenaHC[(i - 1)];
                ordenaHC[(i - 1)] = temp;
            }
        }
    }

    private void ordernarAulas(ProfCH[] aulas, int professor) {
        for (int i = professor; i > 0; i--) {
            if (aulas[i].getCargaHoraria() > aulas[i - 1].getCargaHoraria()) {
                ProfCH temp = aulas[i];
                aulas[i] = aulas[(i - 1)];
                aulas[(i - 1)] = temp;
            }
        }
    }

    /**
     * Method description
     *
     * @param s
     * @param horario
     * @return
     */
    private int calculaSobreposicao(Quadro s, int horario) {
        int _sobreposicao = 0;

        for (int p = 0; p < s.matriz.length; p++) {    // percorrer os professores para saber se ha sobreposicao
            s.colunas[p] = s.matriz[p][horario];       // copiar a coluna
        }

        Arrays.sort(s.colunas);

        int temp = 0;    // so tem direito a 1 sobreposicao por dia, supomos q � -1

        for (int p = 1; p < s.matriz.length; p++) {        // comecamos da segunda turma
            if (s.colunas[p] > 0) {                        // se tiver uma turma
                if (s.colunas[p] != s.colunas[p - 1]) {    // se for diferente da anterior
                    _sobreposicao += temp;                 // se for zero � pk so tem 1 e nao precisa contar
                    temp = 0;
                } else {                                   // aumenta a contagem
                    ++temp;
                }
            }
        }

        return _sobreposicao;
    }

    /**
     * Method description
     *
     * @param s
     * @param professor
     * @param horario
     * @return
     */
    private int calculaSobreposicao(Quadro s, int professor, int horario) {
        int _sobreposicao = 0;
        int turma = s.matriz[professor][horario];

        for (int p = 0; p < s.matriz.length; p++) {    // percorrer os professores para saber se ha sobreposicao
            if (s.matriz[p][horario] == turma) {
                _sobreposicao++;
            }
        }

        return _sobreposicao - 1;
    }

    /**
     * Method description
     *
     * @param s
     * @param professor
     * @param horario
     * @return
     */
    public int calculaExcessoAulas(Quadro s, int professor, int horario) {
        int _excessoAula = 0;
        int startHorario = (horario / TEMPOS_DIA) * TEMPOS_DIA;
        int turma = s.matriz[professor][horario];

        for (int i = startHorario; i < startHorario + TEMPOS_DIA; i++) {
            if (s.matriz[professor][i] == turma) {
                _excessoAula++;
            }
        }

        return (_excessoAula > 2)
                ? _excessoAula - 2
                : 0;
    }


    public String toString(Quadro quadro) {
        int dia = 0;
        String diasStr = "  |";

        for (int i = 0; i < NOMES_DIAS_SEMANA.length; i++) {
            String d = NOMES_DIAS_SEMANA[i];

            while (d.length() < 18) {
                if (d.length() % 2 == 0) {
                    d = "-" + d;
                } else {
                    d = d + "-";
                }
            }

            // System.out.println(""+d.length());
            diasStr += d + "|";
        }

        String horarioStr = "  |";

        for (int j = 1; j <= TEMPOS_DIA * NOMES_DIAS_SEMANA.length; j++, dia++) {
            String h = "" + j;

            while (h.length() < 3) {
                h = " " + h;
            }

            horarioStr += h;

            if (j % TEMPOS_DIA == 0) {
                horarioStr += "|";
            }
        }

        String s = "";

        s += diasStr + "\n";
        s += horarioStr + "\n";

        for (int i = 0; i < horarioStr.length(); i++) {
            s += "-";
        }

        s += "\n";

        for (int i = 0; i < quadro.getMatriz().length; i++) {
            s += (i < 10)
                    ? i + " "
                    : "" + i;
            s += "|";

            for (int j = 0; j < quadro.getMatriz()[i].length; j++) {
                String v = " " + quadro.getMatriz()[i][j];

                if (quadro.getMatriz()[i][j] == 0) {
                    v = "##";
                } else if (quadro.getMatriz()[i][j] < 0) {
                    v = "--";
                }

                while (v.length() < 3) {
                    v = " " + v;
                }

                s += v;

                if (j % TEMPOS_DIA == TEMPOS_DIA - 1) {
                    s += "|";
                }
            }

            s += "\n";
        }

        s += "Avalicao:" + quadro.getValor() + "\n";
        s += "Sobreposicao:" + quadro.getSobreposicao() + "\n";
        s += "Excesso Aulas:" + quadro.getExcessoaulas() + "\n";
        s += "Gaps:" + quadro.getGaps() + "\n";

        return s;
    }

    private Quadro interturmas(Quadro q) {
        return null;  //To change body of created methods use File | Settings | File Templates.
    }


    private Quadro intraturmas(Quadro q) {
        return null;
    }

    private Quadro intraturmasInterturmas(Quadro q) {
        if (q.getExcessoaulas() != 0) {
            q = intraturmas(q);
            if (q.getExcessoaulas() == 0) q = interturmas(q);
        }
        if (q.getExcessoaulas() == 0) {
            q = intraturmas(q);
            q = interturmas(q);
        }
        return q;
    }

    public Quadro BT_IIm(Quadro q) {
        //melhor solucao obtida ate entao
        Quadro melhor = q.cloneSolucao();
        //contador de iteracoes
        int iter = 0;
        //iteracao mais recente que forneceu q*
        int melhorIter = 0;
        //Lista Tabu
        LinkedList<Movimento> listaTabu = new LinkedList<Movimento>();

        int aspiracao = melhor.getValor();

        while (iter - melhorIter <= buscaTabuMax) {
            //System.out.println("Iteracao" + iter);
            iter++;
            //se ja m o melhor elemento de N(Q) tal que o movimento m não seja tabu ou Q' atenda a conti��o de aspiracão
            Movimento m = melhorVizinho(q, listaTabu, aspiracao);
            //System.out.println("Movimento" + m);
            listaTabu.addLast(m);
            if (listaTabu.size() > getTamanhoListaTabu())
                listaTabu.removeFirst();

            q.actualizar(m);//Q<-Q'
//            if (q.getSobreposicao() == 0) {//se encontrar um movimento sem sobreposicão faz
//                q = intraturmasInterturmas(q);
//            }

            //System.out.println("Movimento"+m);

            if (q.getValor() < melhor.getValor()) {
                System.out.println("\t" + iter + "\t" + q.getValor() + "----------------------" + melhor.getValor());
                System.out.println("melhorIter" + melhorIter + "|" + iter);
                melhor = q.cloneSolucao();
                aspiracao = melhor.getValor();//actualizar a funcão de aspiracão
                melhorIter = iter;
            }
        }
        System.out.println("Iterator:" + iter);
        return melhor;
    }

    public Quadro graspBuscaTabu() {
        Quadro melhor = null;
        int melhorValor = Integer.MAX_VALUE;
        Random oRandom = new Random();

        System.out.println("i\t alpha \tmelhorValor\t Valor actual");

        for (int i = 0; i < graspMAX; i++) {
            Quadro s = construaSolucaoInicial();
            avaliar(s);
            System.out.println("Descendo....\n");
            s = BT_IIm(s);
            System.out.println("...." + s.getValor());
            if (melhorValor > s.getValor()) {
                System.out.println("Iteracão i=\t" + i + "\t melhor =" + melhorValor + "\t actual=" + s.getValor());
                melhor = s;
                melhorValor = s.getValor();
            }
        }
        return melhor;
    }

    public Movimento melhorVizinho(Quadro q, LinkedList<Movimento> movimentosTabu, int aspiracao) {
        Movimento melhor = new Movimento();
        melhor.valor = Integer.MAX_VALUE;
        Movimento vizinho = new Movimento();
        for (int p = 0; p < q.matriz.length; p++) {//todos os professores, linhas = professores
            vizinho.professor = p;
            for (int i = 0; i < q.matriz[0].length; i++) {//colunas horarios
                if (q.matriz[p][i] < 0) continue;//se for um lugar ocupado
                vizinho.horario1 = i;
                for (int j = i + 1; j < q.matriz[0].length; j++) {//colunas horarios
                    if (q.matriz[p][j] < 0) continue;//se for um lugar ocupado
                    vizinho.horario2 = j;

                    vizinho.sobreposicao = q.sobreposicao;
                    vizinho.gaps = q.gaps;
                    vizinho.excessoaulas = q.excessoaulas;

                    //calcuando as penalidades das colunas e linha mexidas
                    int sobreposicaoI = calculaSobreposicao(q, i);
                    int sobreposicaoJ = calculaSobreposicao(q, j);
                    calculaGapsAndExcessoaulas(q, p);
                    int excessoaulasProfessor = q.tempExcessoaulas;
                    int gapsProfessor = q.tempGaps;

                    //trocando
                    q.troca(p, i, j);

                    //actalizando as penalidades

                    calculaGapsAndExcessoaulas(q, p);


                    vizinho.sobreposicao = vizinho.sobreposicao - sobreposicaoI - sobreposicaoJ + calculaSobreposicao(q, i) + calculaSobreposicao(q, j);
                    vizinho.gaps = vizinho.gaps - gapsProfessor + q.tempGaps;
                    vizinho.excessoaulas = vizinho.excessoaulas - excessoaulasProfessor + q.tempExcessoaulas;

                    vizinho.valor = getPesoGap() * vizinho.gaps + getPesoExcessoAula() * vizinho.excessoaulas + getPesoSobreposicao() * vizinho.sobreposicao;

                    q.troca(p, i, j);//pondo no estado inicial


                    if (melhor.valor > vizinho.valor) {
                        if (vizinho.valor < aspiracao || !movimentosTabu.contains(vizinho)) {
                            //se nao estiver na tabu ou nao for a nossa aspiracao
                            melhor.valor = vizinho.valor;
                            melhor.sobreposicao = vizinho.sobreposicao;
                            melhor.gaps = vizinho.gaps;
                            melhor.excessoaulas = vizinho.excessoaulas;

                            melhor.professor = p;
                            melhor.horario1 = i;
                            melhor.horario2 = j;
                        }
                    }
                }
            }
        }

        return melhor;


    }

    public void calculaGapsAndExcessoaulas(Quadro q, int professor) {
        //Systemelhor.out.println("Professor"+i);
        q.tempGaps = 0;
        q.tempExcessoaulas = 0;
        int[] turmas = new int[horarioAlgo.getNumTurmas() + 1];
        for (int primeirodia = 0; primeirodia < q.matriz[0].length; primeirodia += DIAS_SEMANA) {
            int j;
            int dia = primeirodia;
            for (j = 0, dia = primeirodia; j < DIAS_SEMANA; j++, dia++) {
                if (q.matriz[professor][dia] > 0)
                    turmas[q.matriz[professor][dia]] = 1;
                //q.oHashSet.add(q.matriz[professor][dia]);
            }

            //for (Integer t : q.oHashSet) {
            for (int i = 0; i < turmas.length; i++) {
                if (turmas[i] == 0) continue;
                //System.out.println(""+t);
                int _quantos = -2;
                int _gaps = -1;
                for (j = 0, dia = primeirodia; j < DIAS_SEMANA; j++, dia++) {
                    if (q.matriz[professor][dia] == i) {
                        ++_quantos;
                        if (dia == primeirodia)
                            ++_gaps;
                        else if (q.matriz[professor][dia - 1] != i)
                            ++_gaps;
                    }
                }
                if (_quantos > 0)
                    q.tempExcessoaulas += _quantos;//numero de horas a +
                if (_gaps > 0)
                    q.tempGaps += _gaps;

                //this.setExcessoaulas(this.getExcessoaulas() + quantos);
                //this.setGaps(this.getGaps() + gaps);

            }
        }

    }

//    public static void main(String[] args) {
//
//        HorarioSettings horarioSettings = new HorarioSettings();
//        horarioSettings.setDIAS_SEMANA(5);
//        horarioSettings.setTemposDia(6);
//        Quadro s = new Quadro(17, horarioSettings.getHorariosSemana(), horarioSettings.getTemposDia());
//        AlgoritmosManager2 algoritmosManager2 = new AlgoritmosManager2(horarioSettings);
//        //algoritmosManager2.setHorarioSettings(horarioSettings);
//
//        for (int i = 0; i < s.getMatriz().length; i++) {
//            for (int j = 0; j < s.getMatriz()[0].length; j++) {
//                s.getMatriz()[i][j] = 15;
//            }
//
//        }
//        System.out.println(algoritmosManager2.toString(s));

    //    }

    public int getTamanhoListaTabu() {
        return tamanhoListaTabu;
    }


}

